#pragma once

#ifdef __linux__
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <unistd.h>
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
#else	// __WIN32__
#include <WinSock2.h>
#endif
#include <functional>
#include "com.h"

namespace KRPC {

class TCP : public COM
{
public:														//参数bool表示是否是再次重连
	TCP(std::string dstIp, uint16_t dstPort, std::function<void(bool)> onConnect = nullptr)
	{
#ifdef __WIN32__
		WSAData ws;
		WSAStartup(MAKEWORD(2,2), &ws);
#endif
		sockfd = INVALID_SOCKET;
		dstaddr.sin_family = PF_INET;
		dstaddr.sin_addr.s_addr = inet_addr(dstIp.c_str());
		dstaddr.sin_port = htons(dstPort);
		memset(dstaddr.sin_zero, 0, sizeof(dstaddr.sin_zero));
		open();
		this->onConnect = onConnect;
	}

	~TCP()
	{
		close();
#ifdef __WIN32__
		WSACleanup();
#endif
	}

	void open()
	{
		if (sockfd == (int)INVALID_SOCKET) {
			sockfd = socket(PF_INET, SOCK_STREAM, 0);
			connected = false;	
		}
	}

	void close()
	{
		if (sockfd == (int)INVALID_SOCKET)
			return;
#ifdef __linux__
		::close(sockfd);
#else	//__WIN32__
		closesocket(sockfd);
#endif
		sockfd = INVALID_SOCKET;
		connected = false;
	}

	bool connect(const struct sockaddr_in &addr)
	{
		if (!connected) {
			bool again = (sockfd == (int)INVALID_SOCKET);
			open();
#if 0 //阻塞连接
			connected = (::connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)) == 0);
#else // 超时连接
		#ifdef __linux__
			int flags = fcntl(sockfd, F_GETFL, 0);
			if(-1 == fcntl(sockfd, F_SETFL, flags | O_NONBLOCK))
			{
				close();
				return false;
			}
		#else //__WIN32__
			unsigned long flags = 1;
    		ioctlsocket(sockfd, FIONBIO, &flags); //设置为非阻塞模式
    	#endif
    		if (::connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) {
    	#ifdef __linux__
    			if (errno != EINPROGRESS) {
    	#else //__WIN32__
    			if (WSAEWOULDBLOCK != WSAGetLastError()) {
    	#endif
    				close();
    				return false;
    			} else {
    				fd_set fdw;
    				timeval timeout = {2, 0};	//默认超时2s
    				FD_ZERO(&fdw);
					FD_SET(sockfd, &fdw);
					if (select(sockfd + 1, NULL, &fdw, NULL, &timeout) <= 0) {
	    				close();
	    				return false;
	    			}
	    		#ifdef __linux__
	    			if (::connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)) == 0 || errno == EISCONN) {
	    		#else 
	    			if (::connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)) == 0 || WSAGetLastError() == WSAEISCONN) {
	    		#endif
	    				connected = true;
	    			} else {
	    				close();
    					return false;
	    			}
    			}
    		} else {
    			connected = true;
    		}
    	#ifdef __linux__
			if(-1 == fcntl(sockfd, F_SETFL, flags)){/*fix coverity*/}
    	#else //__WIN32__
    		flags = 0;
    		ioctlsocket(sockfd, FIONBIO, &flags); //设置为阻塞模式
    	#endif
#endif
    		if (onConnect)
				onConnect(again);
		}
		return connected;
	}

	virtual bool recv(Packet &packet, int msec) override
	{
		if (!connect(dstaddr))
			return false;
		setRcvTimeout(msec);
		MsgHead head;
		auto rcvsz = ::recv(sockfd, (char *)&head, sizeof(MsgHead), 0);
		auto RecvDeal = [&rcvsz, this]()->bool{
			if (rcvsz < 0) {
#ifdef __linux__
				if (errno != EWOULDBLOCK) {
#else // __WIN32__
				if (WSAETIMEDOUT != WSAGetLastError()) {
#endif
					close();	//如果不是由于超时引起的读取失败则关闭连接
				} 
				return false;
			} else if (rcvsz == 0) {
				close();
				return false;
			} else {
				return true;
			}
		};

		if (!RecvDeal() || rcvsz != sizeof(MsgHead) || head.len < sizeof(MsgHead)) 
			return false;
		packet.resize(head.len);
		packet.write((const char *)&head, sizeof(MsgHead));

		auto leftsz = head.len - sizeof(MsgHead);
		std::size_t offset = sizeof(MsgHead);
		while (leftsz > 0) {
			rcvsz = ::recv(sockfd, (char *)packet.data() + offset, leftsz, 0);
			if (!RecvDeal()) 
				return false;
			offset += rcvsz;
			leftsz -= rcvsz;
			packet.reset(offset);
		}
		return true;
	}

	void setRcvTimeout(int msec)
	{
#ifdef __linux__
		struct timeval tv = {msec / 1000, 1000 * (msec % 1000)};
		if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))){/*fix coverity*/}
#else 	//WINDOWS设置超时参数为一个整数
		setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&msec, sizeof(msec));
#endif
	}

	virtual bool send(const Packet &packet) override
	{
		if (!socketIsVaild()) {
			//看上去这个com只有主动调用，如果对端关闭，需要主动检查套接字是否还有效
			//解决对端如果重新监听，第一次调用一定失败问题
			close();
		}

		auto sndsz = (ssize_t)packet.size();
		bool ret = connect(dstaddr) ? (::send(sockfd, (const char *)packet.data(), sndsz, 0) == sndsz) : false;
		if (!ret) 
			close();
		return ret;
	}
	
	virtual bool isLocal() const 
	{
		return dstaddr.sin_addr.s_addr == 0x100007f;//0x100007f为127.0.0.1的网络字节序
	}

	bool socketIsVaild()
	{
		if (sockfd == (int)INVALID_SOCKET)
			return false;

#ifdef __linux__
		struct tcp_info info;
		int len=sizeof(info);
		if (-1 == getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len))
			return false;
		if ((info.tcpi_state == TCP_ESTABLISHED))
			return true;
#else
		return true;
#endif

		return false;
	}

private:
	int sockfd;
	bool connected;
	struct sockaddr_in dstaddr;
	std::function<void(bool)> onConnect;
};

}
